Introduction to Artificial Neural Networks (ANN)
Artificial Neural Networks (ANN) are computing systems inspired by
the biological neural networks of animal brains. These models are
designed to simulate the way the human brain processes
information, making them exceptionally good at modeling and
solving complex problems by learning from data.
Applications of ANNs
ANNs are applied in a wide range of real-world scenarios, some
of which include:
-
Netflix content recommendation: Uses ANNs to
analyze your viewing history and preferences to recommend
movies and shows you’re likely to enjoy.
-
Instagram feed: Employs deep learning
algorithms to personalize your feed, showing you content that
is more relevant to your interests.
-
Handwritten digit recognition: ANNs can
identify and classify handwritten numbers, a technology used
in document digitization and character recognition.
-
FaceID technology: Utilizes advanced ANNs for
secure and accurate facial recognition, allowing you to unlock
your device by simply looking at it.
These examples demonstrate the versatility and the ability of
ANNs to learn from large datasets and perform tasks that would
require human intelligence.
Exercise 1: Did I Pass the Subject?
This exercise focuses on using an ANN to predict whether a
student has passed a subject based on their academic features,
such as grades obtained in assignments, midterm exams, and class
attendance.
1. Install Packages and Call Libraries
Before we start working with artificial neural networks in R,
it’s crucial to have the neuralnet package
installed and loaded. This package allows us to efficiently
train and simulate artificial neural networks in R with a
relatively straightforward syntax. The following code chunk
first checks if the neuralnet package is installed,
installing it if necessary. Then, it loads the package using
library(neuralnet), enabling us to access its
functions and use them in our analysis.
Ensure you have an active internet connection if you need to
install the package, as install.packages will
download the package from CRAN.
# install.packages("dplyr")
# install.packages("neuralnet")
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(neuralnet)
##
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
##
## compute
2. Obtain Data
We are constructing a data frame named df that
contains three columns: exam, proyect,
and status. Each of these columns represents
different aspects of a dataset that could be related to student
assessments or project evaluations:
-
The exam column contains numeric scores or
values associated with exams, with each entry representing a
different individual or instance.
-
The proyect column (presumably intended to
spell “project”) includes scores or values related to
projects. Like the exam column, each entry here
pertains to a different individual or instance.
-
The status column uses binary values (1 or 0)
to indicate a particular state or condition for each entry.
This could represent a pass/fail status, completion, or any
other binary indicator relevant to the context of the data.
The code below combines these vectors into a single data frame,
providing a structured and tabular representation of the data
for further analysis or visualization.
exam<-c(20,10,30,20,80,30)
proyect<-c(90,20,40,50,50,80)
status<-c(1,0,0,0,0,1)
df<-data.frame(exam,proyect,status)
3. Generate Neural Network
We’re applying a neural network model to predict the
status based on exam and
proyect scores using the
neuralnet package. This model helps us understand
the relationship between exam/project scores and their status
outcomes. After training the model with
nn1 <- neuralnet(status ~., data=df), we
visualize it using
plot(nn1, rep="best") to inspect the
network structure and how inputs are related to the prediction.
nn1 <- neuralnet(status ~., data=df)
plot(nn1, rep="best")
4. Predict Results
Now that our neural network model is trained, we proceed to
evaluate its predictive performance using a new set of exam and
project scores. This step is essential for assessing the model’s
ability to generalize and make accurate predictions on data it
hasn’t seen before. By inputting these new scores into the
model, we aim to predict their corresponding statuses, providing
insights into how well our model can apply learned patterns to
real-world or hypothetical scenarios. This evaluation phase is
crucial for understanding the practical applicability of our
neural network in predicting outcomes based on exam and project
performances.
exam_test<-c(30,40,85)
proyect_test<-c(85,50,40)
test1 <- data_frame(exam_test,proyect_test)
## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## ℹ Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
prediction <- compute(nn1,test1)
prediction$net.result
## [,1]
## [1,] 1.01126893
## [2,] 0.01946212
## [3,] -0.01891719
1. Install Packages and Call Libraries
Before diving into our analysis, it’s crucial to ensure that all
necessary packages are installed and loaded into our R
environment. This step lays the groundwork for a smooth analysis
process, allowing us to utilize various functions and tools
provided by these packages. Specifically, we focus on two
packages:
-
readr: Facilitates efficient reading of rectangular data, such as
CSV files. Its fast and friendly syntax makes it a go-to
choice for data import in R.
-
caret: Stands for Classification And REgression Training.
This package provides a comprehensive suite of tools to create
complex predictive models. It simplifies the process of
training and tuning machine learning models, making it
invaluable for predictive analytics.
By loading these libraries, we ensure that our R session is
equipped with the necessary tools for data importation,
preprocessing, and advanced modeling techniques.
# install.packages("readr")
# install.packages("caret")
library(readr)
library(caret)
2. Obtain Data
After setting up our environment with the necessary libraries,
our next step involves loading and preparing the dataset for
analysis. We’re focusing on a dataset named
cancer_de_mama.csv, which includes data pertinent
to breast cancer diagnosis. The preparation process involves
several key steps to ensure the data is suitable for training a
machine learning model:
-
Loading the Dataset: We use the
read_csv function from the
readr package to load the
cancer_de_mama.csv file. This function is
optimized for fast and efficient loading of CSV files into
R.
-
Encoding the Diagnosis: The dataset
includes a diagnosis column with categorical values (M
for malignant, B for benign). We convert these
into binary numeric values (1 for malignant, 0 for benign)
to facilitate the modeling process. This encoding simplifies
the use of algorithms that require numerical input.
-
Splitting the Data: To evaluate our model’s
performance accurately, we split the dataset into training
and testing sets. Using the
createDataPartition function from the
caret package, we allocate 75% of the data for
training and reserve 25% for testing. This split is based on
the diagnosis column to ensure that both sets are
representative of the overall dataset.
-
Reproducibility: We set a seed before
splitting the data to ensure that our results are
reproducible. This step is crucial for scientific rigor and
allows others to replicate our analysis exactly.
The following code snippet illustrates these steps:
breast_cancer <- read_csv("cancer_de_mama.csv")
breast_cancer$diagnosis <- ifelse(breast_cancer$diagnosis == "M", 1, 0)
# Create indices for a 75% train and 25% test split
set.seed(123) # Setting seed for reproducibility
trainIndex <- createDataPartition(breast_cancer$diagnosis, p = 0.75,
list = FALSE,
times = 1)
# Split the data into training and testing sets
trainSet <- breast_cancer[trainIndex, ]
testSet <- breast_cancer[-trainIndex, ]
head(breast_cancer)
## # A tibble: 6 × 31
## diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 18.0 10.4 123. 1001 0.118
## 2 1 20.6 17.8 133. 1326 0.0847
## 3 1 19.7 21.2 130 1203 0.110
## 4 1 11.4 20.4 77.6 386. 0.142
## 5 1 20.3 14.3 135. 1297 0.100
## 6 1 12.4 15.7 82.6 477. 0.128
## # ℹ 25 more variables: compactness_mean <dbl>, concavity_mean <dbl>,
## # `concave points_mean` <dbl>, symmetry_mean <dbl>,
## # fractal_dimension_mean <dbl>, radius_se <dbl>, texture_se <dbl>,
## # perimeter_se <dbl>, area_se <dbl>, smoothness_se <dbl>,
## # compactness_se <dbl>, concavity_se <dbl>, `concave points_se` <dbl>,
## # symmetry_se <dbl>, fractal_dimension_se <dbl>, radius_worst <dbl>,
## # texture_worst <dbl>, perimeter_worst <dbl>, area_worst <dbl>, …
3. Clean Data
After splitting the breast_cancer dataset into
training and testing sets, further refinements are made to
ensure smooth model training and evaluation:
-
Standardizing Variable Names: To prevent
potential issues with variable names that might not conform
to R’s variable naming conventions (such as spaces or
special characters), we use make.names with the
unique = TRUE parameter. This function modifies
the names in both training and testing datasets to ensure
they are valid R identifiers and unique. This step is
crucial for avoiding errors during model training and
evaluation.
-
Preparing a Blind Testing Set: To evaluate
the model’s predictive performance objectively, we create a
‘blind’ testing set. This version of the testing set
excludes the diagnosis column, which is the
outcome variable our model aims to predict. The absence of
this variable ensures that our model predictions are made
without access to the true outcomes, mimicking a real-world
scenario where the model would be used to predict unknown
cases.
The code modifications applied to the variable names and the
preparation of the blind testing set are foundational for the
subsequent modeling steps. They help in ensuring that our
analysis pipeline is robust and that the evaluation of the
model’s performance is as realistic as possible.
names(trainSet) <- make.names(names(trainSet), unique = TRUE)
names(testSet) <- make.names(names(trainSet), unique = TRUE)
testSetBlind <- subset(testSet, select = -diagnosis)
4. Generate Neural Network
With our data preprocessed and split into training and testing
sets, we proceed to train a neural network model. Our objective
is to predict the binary diagnosis outcome (malignant or benign)
based on a range of input features. The model is configured with
a single hidden layer consisting of five neurons, a setup aimed
at capturing the underlying complexities in the data without
overly complicating the model.
Following the model training, we visualize the neural network.
This visualization serves as a crucial step for understanding
the model’s structure, including how inputs are processed
through hidden layers to produce the final prediction. It
provides us with a graphical representation of the model,
showcasing the neurons, layers, and their interconnections.
This trained model and its visualization allow us to closely
examine the neural network’s architecture and better understand
the factors influencing its predictions. It sets the stage for
the next steps, where we will assess the model’s performance on
unseen data and gauge its predictive accuracy.
nn2 <- neuralnet(diagnosis ~ ., data=trainSet, hidden=c(5), linear.output=FALSE)
plot(nn2, rep="best")
5. Predict Results
After predicting the outcomes for our blind testing set using
the neural network model, we proceed to assess the model’s
performance through several key metrics: accuracy, recall, and
specificity. These metrics collectively offer a comprehensive
view of the model’s predictive capabilities:
-
Accuracy provides a high-level overview of
the model’s overall performance, indicating how often it
predicts correctly.
-
Recall is especially important in medical
diagnostics, as it measures the model’s ability to identify
all relevant cases of malignancy.
-
Specificity complements recall by assessing
the model’s proficiency in correctly identifying benign cases,
thus avoiding unnecessary alarm.
The computation of these metrics is based on a confusion matrix,
which contrasts the predicted labels against the actual labels
from the testing set. This analysis yields valuable insights
into the model’s strengths and areas for improvement, especially
in the context of breast cancer diagnosis where the balance
between sensitivity and specificity is critical.
prediction <- compute(nn2,testSetBlind)
predicted_labels <- ifelse(prediction$net.result > 0.5, 1, 0)
true_labels <- testSet$diagnosis
conf_matrix <- table(Predicted = predicted_labels, Actual = true_labels)
# Accuracy Metrics
accuracy <- sum(diag(conf_matrix)) / sum(conf_matrix)
recall <- conf_matrix[2,2] / sum(conf_matrix[2,])
specificity <- conf_matrix[1,1] / sum(conf_matrix[1,])
cat("Accuracy:", accuracy, "\n")
## Accuracy: 0.9859155
cat("Recall:", recall, "\n")
## Recall: 1
cat("Specificity:", specificity, "\n")
## Specificity: 0.9753086
LS0tCnRpdGxlOiAiUmVkZXMgTmV1cm9uYWxlcyIKYXV0aG9yOiAiRGF2aWQgRG9taW5ndWV6IC0gQTAxNTcwOTc1IgpkYXRlOiAiMjAyNC0wMi0yMiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKIVtdKC9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy9BTk4vMSotZUxqUFk3VUdTb1FoU3lXNXFDNmd3LmdpZikKCiMgSW50cm9kdWN0aW9uIHRvIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmtzIChBTk4pCgpBcnRpZmljaWFsIE5ldXJhbCBOZXR3b3JrcyAoQU5OKSBhcmUgY29tcHV0aW5nIHN5c3RlbXMgaW5zcGlyZWQgYnkgdGhlIGJpb2xvZ2ljYWwgbmV1cmFsIG5ldHdvcmtzIG9mIGFuaW1hbCBicmFpbnMuIFRoZXNlIG1vZGVscyBhcmUgZGVzaWduZWQgdG8gc2ltdWxhdGUgdGhlIHdheSB0aGUgaHVtYW4gYnJhaW4gcHJvY2Vzc2VzIGluZm9ybWF0aW9uLCBtYWtpbmcgdGhlbSBleGNlcHRpb25hbGx5IGdvb2QgYXQgbW9kZWxpbmcgYW5kIHNvbHZpbmcgY29tcGxleCBwcm9ibGVtcyBieSBsZWFybmluZyBmcm9tIGRhdGEuCgojIyBBcHBsaWNhdGlvbnMgb2YgQU5OcwoKQU5OcyBhcmUgYXBwbGllZCBpbiBhIHdpZGUgcmFuZ2Ugb2YgcmVhbC13b3JsZCBzY2VuYXJpb3MsIHNvbWUgb2Ygd2hpY2ggaW5jbHVkZToKCi0gKipOZXRmbGl4IGNvbnRlbnQgcmVjb21tZW5kYXRpb24qKjogVXNlcyBBTk5zIHRvIGFuYWx5emUgeW91ciB2aWV3aW5nIGhpc3RvcnkgYW5kIHByZWZlcmVuY2VzIHRvIHJlY29tbWVuZCBtb3ZpZXMgYW5kIHNob3dzIHlvdSdyZSBsaWtlbHkgdG8gZW5qb3kuCi0gKipJbnN0YWdyYW0gZmVlZCoqOiBFbXBsb3lzIGRlZXAgbGVhcm5pbmcgYWxnb3JpdGhtcyB0byBwZXJzb25hbGl6ZSB5b3VyIGZlZWQsIHNob3dpbmcgeW91IGNvbnRlbnQgdGhhdCBpcyBtb3JlIHJlbGV2YW50IHRvIHlvdXIgaW50ZXJlc3RzLgotICoqSGFuZHdyaXR0ZW4gZGlnaXQgcmVjb2duaXRpb24qKjogQU5OcyBjYW4gaWRlbnRpZnkgYW5kIGNsYXNzaWZ5IGhhbmR3cml0dGVuIG51bWJlcnMsIGEgdGVjaG5vbG9neSB1c2VkIGluIGRvY3VtZW50IGRpZ2l0aXphdGlvbiBhbmQgY2hhcmFjdGVyIHJlY29nbml0aW9uLgotICoqRmFjZUlEIHRlY2hub2xvZ3kqKjogVXRpbGl6ZXMgYWR2YW5jZWQgQU5OcyBmb3Igc2VjdXJlIGFuZCBhY2N1cmF0ZSBmYWNpYWwgcmVjb2duaXRpb24sIGFsbG93aW5nIHlvdSB0byB1bmxvY2sgeW91ciBkZXZpY2UgYnkgc2ltcGx5IGxvb2tpbmcgYXQgaXQuCgpUaGVzZSBleGFtcGxlcyBkZW1vbnN0cmF0ZSB0aGUgdmVyc2F0aWxpdHkgYW5kIHRoZSBhYmlsaXR5IG9mIEFOTnMgdG8gbGVhcm4gZnJvbSBsYXJnZSBkYXRhc2V0cyBhbmQgcGVyZm9ybSB0YXNrcyB0aGF0IHdvdWxkIHJlcXVpcmUgaHVtYW4gaW50ZWxsaWdlbmNlLgoKIyMgRXhlcmNpc2UgMTogRGlkIEkgUGFzcyB0aGUgU3ViamVjdD8KClRoaXMgZXhlcmNpc2UgZm9jdXNlcyBvbiB1c2luZyBhbiBBTk4gdG8gcHJlZGljdCB3aGV0aGVyIGEgc3R1ZGVudCBoYXMgcGFzc2VkIGEgc3ViamVjdCBiYXNlZCBvbiB0aGVpciBhY2FkZW1pYyBmZWF0dXJlcywgc3VjaCBhcyBncmFkZXMgb2J0YWluZWQgaW4gYXNzaWdubWVudHMsIG1pZHRlcm0gZXhhbXMsIGFuZCBjbGFzcyBhdHRlbmRhbmNlLgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+MS4gSW5zdGFsbCBQYWNrYWdlcyBhbmQgQ2FsbCBMaWJyYXJpZXM8L3NwYW4+CkJlZm9yZSB3ZSBzdGFydCB3b3JraW5nIHdpdGggYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29ya3MgaW4gUiwgaXQncyBjcnVjaWFsIHRvIGhhdmUgdGhlIGBuZXVyYWxuZXRgIHBhY2thZ2UgaW5zdGFsbGVkIGFuZCBsb2FkZWQuIFRoaXMgcGFja2FnZSBhbGxvd3MgdXMgdG8gZWZmaWNpZW50bHkgdHJhaW4gYW5kIHNpbXVsYXRlIGFydGlmaWNpYWwgbmV1cmFsIG5ldHdvcmtzIGluIFIgd2l0aCBhIHJlbGF0aXZlbHkgc3RyYWlnaHRmb3J3YXJkIHN5bnRheC4gVGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIGZpcnN0IGNoZWNrcyBpZiB0aGUgYG5ldXJhbG5ldGAgcGFja2FnZSBpcyBpbnN0YWxsZWQsIGluc3RhbGxpbmcgaXQgaWYgbmVjZXNzYXJ5LiBUaGVuLCBpdCBsb2FkcyB0aGUgcGFja2FnZSB1c2luZyBgbGlicmFyeShuZXVyYWxuZXQpYCwgZW5hYmxpbmcgdXMgdG8gYWNjZXNzIGl0cyBmdW5jdGlvbnMgYW5kIHVzZSB0aGVtIGluIG91ciBhbmFseXNpcy4KCkVuc3VyZSB5b3UgaGF2ZSBhbiBhY3RpdmUgaW50ZXJuZXQgY29ubmVjdGlvbiBpZiB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZSBwYWNrYWdlLCBhcyBgaW5zdGFsbC5wYWNrYWdlc2Agd2lsbCBkb3dubG9hZCB0aGUgcGFja2FnZSBmcm9tIENSQU4uCgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQojIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobmV1cmFsbmV0KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPjIuIE9idGFpbiBEYXRhPC9zcGFuPgpXZSBhcmUgY29uc3RydWN0aW5nIGEgZGF0YSBmcmFtZSBuYW1lZCBgZGZgIHRoYXQgY29udGFpbnMgdGhyZWUgY29sdW1uczogYGV4YW1gLCBgcHJveWVjdGAsIGFuZCBgc3RhdHVzYC4gRWFjaCBvZiB0aGVzZSBjb2x1bW5zIHJlcHJlc2VudHMgZGlmZmVyZW50IGFzcGVjdHMgb2YgYSBkYXRhc2V0IHRoYXQgY291bGQgYmUgcmVsYXRlZCB0byBzdHVkZW50IGFzc2Vzc21lbnRzIG9yIHByb2plY3QgZXZhbHVhdGlvbnM6CgotIFRoZSBgZXhhbWAgY29sdW1uIGNvbnRhaW5zIG51bWVyaWMgc2NvcmVzIG9yIHZhbHVlcyBhc3NvY2lhdGVkIHdpdGggZXhhbXMsIHdpdGggZWFjaCBlbnRyeSByZXByZXNlbnRpbmcgYSBkaWZmZXJlbnQgaW5kaXZpZHVhbCBvciBpbnN0YW5jZS4KCi0gVGhlIGBwcm95ZWN0YCBjb2x1bW4gKHByZXN1bWFibHkgaW50ZW5kZWQgdG8gc3BlbGwgInByb2plY3QiKSBpbmNsdWRlcyBzY29yZXMgb3IgdmFsdWVzIHJlbGF0ZWQgdG8gcHJvamVjdHMuIExpa2UgdGhlIGBleGFtYCBjb2x1bW4sIGVhY2ggZW50cnkgaGVyZSBwZXJ0YWlucyB0byBhIGRpZmZlcmVudCBpbmRpdmlkdWFsIG9yIGluc3RhbmNlLgoKLSBUaGUgYHN0YXR1c2AgY29sdW1uIHVzZXMgYmluYXJ5IHZhbHVlcyAoMSBvciAwKSB0byBpbmRpY2F0ZSBhIHBhcnRpY3VsYXIgc3RhdGUgb3IgY29uZGl0aW9uIGZvciBlYWNoIGVudHJ5LiBUaGlzIGNvdWxkIHJlcHJlc2VudCBhIHBhc3MvZmFpbCBzdGF0dXMsIGNvbXBsZXRpb24sIG9yIGFueSBvdGhlciBiaW5hcnkgaW5kaWNhdG9yIHJlbGV2YW50IHRvIHRoZSBjb250ZXh0IG9mIHRoZSBkYXRhLgoKVGhlIGNvZGUgYmVsb3cgY29tYmluZXMgdGhlc2UgdmVjdG9ycyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUsIHByb3ZpZGluZyBhIHN0cnVjdHVyZWQgYW5kIHRhYnVsYXIgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRhdGEgZm9yIGZ1cnRoZXIgYW5hbHlzaXMgb3IgdmlzdWFsaXphdGlvbi4KCgpgYGB7cn0KZXhhbTwtYygyMCwxMCwzMCwyMCw4MCwzMCkKcHJveWVjdDwtYyg5MCwyMCw0MCw1MCw1MCw4MCkKc3RhdHVzPC1jKDEsMCwwLDAsMCwxKQoKZGY8LWRhdGEuZnJhbWUoZXhhbSxwcm95ZWN0LHN0YXR1cykKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij4zLiBHZW5lcmF0ZSBOZXVyYWwgTmV0d29yazwvc3Bhbj4KV2UncmUgYXBwbHlpbmcgYSBuZXVyYWwgbmV0d29yayBtb2RlbCB0byBwcmVkaWN0IHRoZSBgc3RhdHVzYCBiYXNlZCBvbiBgZXhhbWAgYW5kIGBwcm95ZWN0YCBzY29yZXMgdXNpbmcgdGhlIGBuZXVyYWxuZXRgIHBhY2thZ2UuIFRoaXMgbW9kZWwgaGVscHMgdXMgdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZXhhbS9wcm9qZWN0IHNjb3JlcyBhbmQgdGhlaXIgc3RhdHVzIG91dGNvbWVzLiBBZnRlciB0cmFpbmluZyB0aGUgbW9kZWwgd2l0aCBgbm4xIDwtIG5ldXJhbG5ldChzdGF0dXMgfi4sIGRhdGE9ZGYpYCwgd2UgdmlzdWFsaXplIGl0IHVzaW5nIGBwbG90KG5uMSwgcmVwPSJiZXN0IilgIHRvIGluc3BlY3QgdGhlIG5ldHdvcmsgc3RydWN0dXJlIGFuZCBob3cgaW5wdXRzIGFyZSByZWxhdGVkIHRvIHRoZSBwcmVkaWN0aW9uLgoKYGBge3J9Cm5uMSA8LSBuZXVyYWxuZXQoc3RhdHVzIH4uLCBkYXRhPWRmKQpwbG90KG5uMSwgcmVwPSJiZXN0IikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij40LiBQcmVkaWN0IFJlc3VsdHM8L3NwYW4+Ck5vdyB0aGF0IG91ciBuZXVyYWwgbmV0d29yayBtb2RlbCBpcyB0cmFpbmVkLCB3ZSBwcm9jZWVkIHRvIGV2YWx1YXRlIGl0cyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIHVzaW5nIGEgbmV3IHNldCBvZiBleGFtIGFuZCBwcm9qZWN0IHNjb3Jlcy4gVGhpcyBzdGVwIGlzIGVzc2VudGlhbCBmb3IgYXNzZXNzaW5nIHRoZSBtb2RlbCdzIGFiaWxpdHkgdG8gZ2VuZXJhbGl6ZSBhbmQgbWFrZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucyBvbiBkYXRhIGl0IGhhc24ndCBzZWVuIGJlZm9yZS4gQnkgaW5wdXR0aW5nIHRoZXNlIG5ldyBzY29yZXMgaW50byB0aGUgbW9kZWwsIHdlIGFpbSB0byBwcmVkaWN0IHRoZWlyIGNvcnJlc3BvbmRpbmcgc3RhdHVzZXMsIHByb3ZpZGluZyBpbnNpZ2h0cyBpbnRvIGhvdyB3ZWxsIG91ciBtb2RlbCBjYW4gYXBwbHkgbGVhcm5lZCBwYXR0ZXJucyB0byByZWFsLXdvcmxkIG9yIGh5cG90aGV0aWNhbCBzY2VuYXJpb3MuIFRoaXMgZXZhbHVhdGlvbiBwaGFzZSBpcyBjcnVjaWFsIGZvciB1bmRlcnN0YW5kaW5nIHRoZSBwcmFjdGljYWwgYXBwbGljYWJpbGl0eSBvZiBvdXIgbmV1cmFsIG5ldHdvcmsgaW4gcHJlZGljdGluZyBvdXRjb21lcyBiYXNlZCBvbiBleGFtIGFuZCBwcm9qZWN0IHBlcmZvcm1hbmNlcy4KCgpgYGB7cn0KZXhhbV90ZXN0PC1jKDMwLDQwLDg1KQpwcm95ZWN0X3Rlc3Q8LWMoODUsNTAsNDApCnRlc3QxIDwtIGRhdGFfZnJhbWUoZXhhbV90ZXN0LHByb3llY3RfdGVzdCkKcHJlZGljdGlvbiA8LSBjb21wdXRlKG5uMSx0ZXN0MSkKCnByZWRpY3Rpb24kbmV0LnJlc3VsdApgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+MS4gSW5zdGFsbCBQYWNrYWdlcyBhbmQgQ2FsbCBMaWJyYXJpZXM8L3NwYW4+CkJlZm9yZSBkaXZpbmcgaW50byBvdXIgYW5hbHlzaXMsIGl0J3MgY3J1Y2lhbCB0byBlbnN1cmUgdGhhdCBhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgYW5kIGxvYWRlZCBpbnRvIG91ciBSIGVudmlyb25tZW50LiBUaGlzIHN0ZXAgbGF5cyB0aGUgZ3JvdW5kd29yayBmb3IgYSBzbW9vdGggYW5hbHlzaXMgcHJvY2VzcywgYWxsb3dpbmcgdXMgdG8gdXRpbGl6ZSB2YXJpb3VzIGZ1bmN0aW9ucyBhbmQgdG9vbHMgcHJvdmlkZWQgYnkgdGhlc2UgcGFja2FnZXMuIFNwZWNpZmljYWxseSwgd2UgZm9jdXMgb24gdHdvIHBhY2thZ2VzOgoKLSAqKmByZWFkcmAqKjogRmFjaWxpdGF0ZXMgZWZmaWNpZW50IHJlYWRpbmcgb2YgcmVjdGFuZ3VsYXIgZGF0YSwgc3VjaCBhcyBDU1YgZmlsZXMuIEl0cyBmYXN0IGFuZCBmcmllbmRseSBzeW50YXggbWFrZXMgaXQgYSBnby10byBjaG9pY2UgZm9yIGRhdGEgaW1wb3J0IGluIFIuCi0gKipgY2FyZXRgKio6IFN0YW5kcyBmb3IgKkNsYXNzaWZpY2F0aW9uIEFuZCBSRWdyZXNzaW9uIFRyYWluaW5nKi4gVGhpcyBwYWNrYWdlIHByb3ZpZGVzIGEgY29tcHJlaGVuc2l2ZSBzdWl0ZSBvZiB0b29scyB0byBjcmVhdGUgY29tcGxleCBwcmVkaWN0aXZlIG1vZGVscy4gSXQgc2ltcGxpZmllcyB0aGUgcHJvY2VzcyBvZiB0cmFpbmluZyBhbmQgdHVuaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLCBtYWtpbmcgaXQgaW52YWx1YWJsZSBmb3IgcHJlZGljdGl2ZSBhbmFseXRpY3MuCgpCeSBsb2FkaW5nIHRoZXNlIGxpYnJhcmllcywgd2UgZW5zdXJlIHRoYXQgb3VyIFIgc2Vzc2lvbiBpcyBlcXVpcHBlZCB3aXRoIHRoZSBuZWNlc3NhcnkgdG9vbHMgZm9yIGRhdGEgaW1wb3J0YXRpb24sIHByZXByb2Nlc3NpbmcsIGFuZCBhZHZhbmNlZCBtb2RlbGluZyB0ZWNobmlxdWVzLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkciIpCiMgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGNhcmV0KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+Mi4gT2J0YWluIERhdGE8L3NwYW4+CkFmdGVyIHNldHRpbmcgdXAgb3VyIGVudmlyb25tZW50IHdpdGggdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMsIG91ciBuZXh0IHN0ZXAgaW52b2x2ZXMgbG9hZGluZyBhbmQgcHJlcGFyaW5nIHRoZSBkYXRhc2V0IGZvciBhbmFseXNpcy4gV2UncmUgZm9jdXNpbmcgb24gYSBkYXRhc2V0IG5hbWVkIGBjYW5jZXJfZGVfbWFtYS5jc3ZgLCB3aGljaCBpbmNsdWRlcyBkYXRhIHBlcnRpbmVudCB0byBicmVhc3QgY2FuY2VyIGRpYWdub3Npcy4gVGhlIHByZXBhcmF0aW9uIHByb2Nlc3MgaW52b2x2ZXMgc2V2ZXJhbCBrZXkgc3RlcHMgdG8gZW5zdXJlIHRoZSBkYXRhIGlzIHN1aXRhYmxlIGZvciB0cmFpbmluZyBhIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWw6CgotICoqTG9hZGluZyB0aGUgRGF0YXNldCoqOiBXZSB1c2UgdGhlIGByZWFkX2NzdmAgZnVuY3Rpb24gZnJvbSB0aGUgYHJlYWRyYCBwYWNrYWdlIHRvIGxvYWQgdGhlIGBjYW5jZXJfZGVfbWFtYS5jc3ZgIGZpbGUuIFRoaXMgZnVuY3Rpb24gaXMgb3B0aW1pemVkIGZvciBmYXN0IGFuZCBlZmZpY2llbnQgbG9hZGluZyBvZiBDU1YgZmlsZXMgaW50byBSLgoKLSAqKkVuY29kaW5nIHRoZSBEaWFnbm9zaXMqKjogVGhlIGRhdGFzZXQgaW5jbHVkZXMgYSBkaWFnbm9zaXMgY29sdW1uIHdpdGggY2F0ZWdvcmljYWwgdmFsdWVzIChgTWAgZm9yIG1hbGlnbmFudCwgYEJgIGZvciBiZW5pZ24pLiBXZSBjb252ZXJ0IHRoZXNlIGludG8gYmluYXJ5IG51bWVyaWMgdmFsdWVzICgxIGZvciBtYWxpZ25hbnQsIDAgZm9yIGJlbmlnbikgdG8gZmFjaWxpdGF0ZSB0aGUgbW9kZWxpbmcgcHJvY2Vzcy4gVGhpcyBlbmNvZGluZyBzaW1wbGlmaWVzIHRoZSB1c2Ugb2YgYWxnb3JpdGhtcyB0aGF0IHJlcXVpcmUgbnVtZXJpY2FsIGlucHV0LgoKLSAqKlNwbGl0dGluZyB0aGUgRGF0YSoqOiBUbyBldmFsdWF0ZSBvdXIgbW9kZWwncyBwZXJmb3JtYW5jZSBhY2N1cmF0ZWx5LCB3ZSBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMuIFVzaW5nIHRoZSBgY3JlYXRlRGF0YVBhcnRpdGlvbmAgZnVuY3Rpb24gZnJvbSB0aGUgYGNhcmV0YCBwYWNrYWdlLCB3ZSBhbGxvY2F0ZSA3NSUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCByZXNlcnZlIDI1JSBmb3IgdGVzdGluZy4gVGhpcyBzcGxpdCBpcyBiYXNlZCBvbiB0aGUgZGlhZ25vc2lzIGNvbHVtbiB0byBlbnN1cmUgdGhhdCBib3RoIHNldHMgYXJlIHJlcHJlc2VudGF0aXZlIG9mIHRoZSBvdmVyYWxsIGRhdGFzZXQuCgotICoqUmVwcm9kdWNpYmlsaXR5Kio6IFdlIHNldCBhIHNlZWQgYmVmb3JlIHNwbGl0dGluZyB0aGUgZGF0YSB0byBlbnN1cmUgdGhhdCBvdXIgcmVzdWx0cyBhcmUgcmVwcm9kdWNpYmxlLiBUaGlzIHN0ZXAgaXMgY3J1Y2lhbCBmb3Igc2NpZW50aWZpYyByaWdvciBhbmQgYWxsb3dzIG90aGVycyB0byByZXBsaWNhdGUgb3VyIGFuYWx5c2lzIGV4YWN0bHkuCgpUaGUgZm9sbG93aW5nIGNvZGUgc25pcHBldCBpbGx1c3RyYXRlcyB0aGVzZSBzdGVwczoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJyZWFzdF9jYW5jZXIgPC0gcmVhZF9jc3YoImNhbmNlcl9kZV9tYW1hLmNzdiIpCmJyZWFzdF9jYW5jZXIkZGlhZ25vc2lzIDwtIGlmZWxzZShicmVhc3RfY2FuY2VyJGRpYWdub3NpcyA9PSAiTSIsIDEsIDApCgojIENyZWF0ZSBpbmRpY2VzIGZvciBhIDc1JSB0cmFpbiBhbmQgMjUlIHRlc3Qgc3BsaXQKc2V0LnNlZWQoMTIzKSAjIFNldHRpbmcgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihicmVhc3RfY2FuY2VyJGRpYWdub3NpcywgcCA9IDAuNzUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkKCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzCnRyYWluU2V0IDwtIGJyZWFzdF9jYW5jZXJbdHJhaW5JbmRleCwgXQp0ZXN0U2V0IDwtIGJyZWFzdF9jYW5jZXJbLXRyYWluSW5kZXgsIF0KCmhlYWQoYnJlYXN0X2NhbmNlcikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPjMuIENsZWFuIERhdGE8L3NwYW4+CkFmdGVyIHNwbGl0dGluZyB0aGUgYGJyZWFzdF9jYW5jZXJgIGRhdGFzZXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCBmdXJ0aGVyIHJlZmluZW1lbnRzIGFyZSBtYWRlIHRvIGVuc3VyZSBzbW9vdGggbW9kZWwgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb246CgotICoqU3RhbmRhcmRpemluZyBWYXJpYWJsZSBOYW1lcyoqOiBUbyBwcmV2ZW50IHBvdGVudGlhbCBpc3N1ZXMgd2l0aCB2YXJpYWJsZSBuYW1lcyB0aGF0IG1pZ2h0IG5vdCBjb25mb3JtIHRvIFIncyB2YXJpYWJsZSBuYW1pbmcgY29udmVudGlvbnMgKHN1Y2ggYXMgc3BhY2VzIG9yIHNwZWNpYWwgY2hhcmFjdGVycyksIHdlIHVzZSBgbWFrZS5uYW1lc2Agd2l0aCB0aGUgYHVuaXF1ZSA9IFRSVUVgIHBhcmFtZXRlci4gVGhpcyBmdW5jdGlvbiBtb2RpZmllcyB0aGUgbmFtZXMgaW4gYm90aCB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhc2V0cyB0byBlbnN1cmUgdGhleSBhcmUgdmFsaWQgUiBpZGVudGlmaWVycyBhbmQgdW5pcXVlLiBUaGlzIHN0ZXAgaXMgY3J1Y2lhbCBmb3IgYXZvaWRpbmcgZXJyb3JzIGR1cmluZyBtb2RlbCB0cmFpbmluZyBhbmQgZXZhbHVhdGlvbi4KCi0gKipQcmVwYXJpbmcgYSBCbGluZCBUZXN0aW5nIFNldCoqOiBUbyBldmFsdWF0ZSB0aGUgbW9kZWwncyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9iamVjdGl2ZWx5LCB3ZSBjcmVhdGUgYSAnYmxpbmQnIHRlc3Rpbmcgc2V0LiBUaGlzIHZlcnNpb24gb2YgdGhlIHRlc3Rpbmcgc2V0IGV4Y2x1ZGVzIHRoZSBgZGlhZ25vc2lzYCBjb2x1bW4sIHdoaWNoIGlzIHRoZSBvdXRjb21lIHZhcmlhYmxlIG91ciBtb2RlbCBhaW1zIHRvIHByZWRpY3QuIFRoZSBhYnNlbmNlIG9mIHRoaXMgdmFyaWFibGUgZW5zdXJlcyB0aGF0IG91ciBtb2RlbCBwcmVkaWN0aW9ucyBhcmUgbWFkZSB3aXRob3V0IGFjY2VzcyB0byB0aGUgdHJ1ZSBvdXRjb21lcywgbWltaWNraW5nIGEgcmVhbC13b3JsZCBzY2VuYXJpbyB3aGVyZSB0aGUgbW9kZWwgd291bGQgYmUgdXNlZCB0byBwcmVkaWN0IHVua25vd24gY2FzZXMuCgpUaGUgY29kZSBtb2RpZmljYXRpb25zIGFwcGxpZWQgdG8gdGhlIHZhcmlhYmxlIG5hbWVzIGFuZCB0aGUgcHJlcGFyYXRpb24gb2YgdGhlIGJsaW5kIHRlc3Rpbmcgc2V0IGFyZSBmb3VuZGF0aW9uYWwgZm9yIHRoZSBzdWJzZXF1ZW50IG1vZGVsaW5nIHN0ZXBzLiBUaGV5IGhlbHAgaW4gZW5zdXJpbmcgdGhhdCBvdXIgYW5hbHlzaXMgcGlwZWxpbmUgaXMgcm9idXN0IGFuZCB0aGF0IHRoZSBldmFsdWF0aW9uIG9mIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGlzIGFzIHJlYWxpc3RpYyBhcyBwb3NzaWJsZS4KCmBgYHtyfQpuYW1lcyh0cmFpblNldCkgPC0gbWFrZS5uYW1lcyhuYW1lcyh0cmFpblNldCksIHVuaXF1ZSA9IFRSVUUpCgpuYW1lcyh0ZXN0U2V0KSA8LSBtYWtlLm5hbWVzKG5hbWVzKHRyYWluU2V0KSwgdW5pcXVlID0gVFJVRSkKdGVzdFNldEJsaW5kIDwtIHN1YnNldCh0ZXN0U2V0LCBzZWxlY3QgPSAtZGlhZ25vc2lzKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPjMuIEdlbmVyYXRlIE5ldXJhbCBOZXR3b3JrPC9zcGFuPgpXaXRoIG91ciBkYXRhIHByZXByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCB3ZSBwcm9jZWVkIHRvIHRyYWluIGEgbmV1cmFsIG5ldHdvcmsgbW9kZWwuIE91ciBvYmplY3RpdmUgaXMgdG8gcHJlZGljdCB0aGUgYmluYXJ5IGRpYWdub3NpcyBvdXRjb21lIChtYWxpZ25hbnQgb3IgYmVuaWduKSBiYXNlZCBvbiBhIHJhbmdlIG9mIGlucHV0IGZlYXR1cmVzLiBUaGUgbW9kZWwgaXMgY29uZmlndXJlZCB3aXRoIGEgc2luZ2xlIGhpZGRlbiBsYXllciBjb25zaXN0aW5nIG9mIGZpdmUgbmV1cm9ucywgYSBzZXR1cCBhaW1lZCBhdCBjYXB0dXJpbmcgdGhlIHVuZGVybHlpbmcgY29tcGxleGl0aWVzIGluIHRoZSBkYXRhIHdpdGhvdXQgb3Zlcmx5IGNvbXBsaWNhdGluZyB0aGUgbW9kZWwuCgpGb2xsb3dpbmcgdGhlIG1vZGVsIHRyYWluaW5nLCB3ZSB2aXN1YWxpemUgdGhlIG5ldXJhbCBuZXR3b3JrLiBUaGlzIHZpc3VhbGl6YXRpb24gc2VydmVzIGFzIGEgY3J1Y2lhbCBzdGVwIGZvciB1bmRlcnN0YW5kaW5nIHRoZSBtb2RlbCdzIHN0cnVjdHVyZSwgaW5jbHVkaW5nIGhvdyBpbnB1dHMgYXJlIHByb2Nlc3NlZCB0aHJvdWdoIGhpZGRlbiBsYXllcnMgdG8gcHJvZHVjZSB0aGUgZmluYWwgcHJlZGljdGlvbi4gSXQgcHJvdmlkZXMgdXMgd2l0aCBhIGdyYXBoaWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgbW9kZWwsIHNob3djYXNpbmcgdGhlIG5ldXJvbnMsIGxheWVycywgYW5kIHRoZWlyIGludGVyY29ubmVjdGlvbnMuCgpUaGlzIHRyYWluZWQgbW9kZWwgYW5kIGl0cyB2aXN1YWxpemF0aW9uIGFsbG93IHVzIHRvIGNsb3NlbHkgZXhhbWluZSB0aGUgbmV1cmFsIG5ldHdvcmsncyBhcmNoaXRlY3R1cmUgYW5kIGJldHRlciB1bmRlcnN0YW5kIHRoZSBmYWN0b3JzIGluZmx1ZW5jaW5nIGl0cyBwcmVkaWN0aW9ucy4gSXQgc2V0cyB0aGUgc3RhZ2UgZm9yIHRoZSBuZXh0IHN0ZXBzLCB3aGVyZSB3ZSB3aWxsIGFzc2VzcyB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YSBhbmQgZ2F1Z2UgaXRzIHByZWRpY3RpdmUgYWNjdXJhY3kuCmBgYHtyfQpubjIgPC0gbmV1cmFsbmV0KGRpYWdub3NpcyB+IC4sIGRhdGE9dHJhaW5TZXQsIGhpZGRlbj1jKDUpLCBsaW5lYXIub3V0cHV0PUZBTFNFKQpwbG90KG5uMiwgcmVwPSJiZXN0IikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij40LiBQcmVkaWN0IFJlc3VsdHM8L3NwYW4+CkFmdGVyIHByZWRpY3RpbmcgdGhlIG91dGNvbWVzIGZvciBvdXIgYmxpbmQgdGVzdGluZyBzZXQgdXNpbmcgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsLCB3ZSBwcm9jZWVkIHRvIGFzc2VzcyB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSB0aHJvdWdoIHNldmVyYWwga2V5IG1ldHJpY3M6IGFjY3VyYWN5LCByZWNhbGwsIGFuZCBzcGVjaWZpY2l0eS4gVGhlc2UgbWV0cmljcyBjb2xsZWN0aXZlbHkgb2ZmZXIgYSBjb21wcmVoZW5zaXZlIHZpZXcgb2YgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBjYXBhYmlsaXRpZXM6CgotICoqQWNjdXJhY3kqKiBwcm92aWRlcyBhIGhpZ2gtbGV2ZWwgb3ZlcnZpZXcgb2YgdGhlIG1vZGVsJ3Mgb3ZlcmFsbCBwZXJmb3JtYW5jZSwgaW5kaWNhdGluZyBob3cgb2Z0ZW4gaXQgcHJlZGljdHMgY29ycmVjdGx5LgotICoqUmVjYWxsKiogaXMgZXNwZWNpYWxseSBpbXBvcnRhbnQgaW4gbWVkaWNhbCBkaWFnbm9zdGljcywgYXMgaXQgbWVhc3VyZXMgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBpZGVudGlmeSBhbGwgcmVsZXZhbnQgY2FzZXMgb2YgbWFsaWduYW5jeS4KLSAqKlNwZWNpZmljaXR5KiogY29tcGxlbWVudHMgcmVjYWxsIGJ5IGFzc2Vzc2luZyB0aGUgbW9kZWwncyBwcm9maWNpZW5jeSBpbiBjb3JyZWN0bHkgaWRlbnRpZnlpbmcgYmVuaWduIGNhc2VzLCB0aHVzIGF2b2lkaW5nIHVubmVjZXNzYXJ5IGFsYXJtLgoKVGhlIGNvbXB1dGF0aW9uIG9mIHRoZXNlIG1ldHJpY3MgaXMgYmFzZWQgb24gYSBjb25mdXNpb24gbWF0cml4LCB3aGljaCBjb250cmFzdHMgdGhlIHByZWRpY3RlZCBsYWJlbHMgYWdhaW5zdCB0aGUgYWN0dWFsIGxhYmVscyBmcm9tIHRoZSB0ZXN0aW5nIHNldC4gVGhpcyBhbmFseXNpcyB5aWVsZHMgdmFsdWFibGUgaW5zaWdodHMgaW50byB0aGUgbW9kZWwncyBzdHJlbmd0aHMgYW5kIGFyZWFzIGZvciBpbXByb3ZlbWVudCwgZXNwZWNpYWxseSBpbiB0aGUgY29udGV4dCBvZiBicmVhc3QgY2FuY2VyIGRpYWdub3NpcyB3aGVyZSB0aGUgYmFsYW5jZSBiZXR3ZWVuIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBpcyBjcml0aWNhbC4KCmBgYHtyfQpwcmVkaWN0aW9uIDwtIGNvbXB1dGUobm4yLHRlc3RTZXRCbGluZCkKCnByZWRpY3RlZF9sYWJlbHMgPC0gaWZlbHNlKHByZWRpY3Rpb24kbmV0LnJlc3VsdCA+IDAuNSwgMSwgMCkKdHJ1ZV9sYWJlbHMgPC0gdGVzdFNldCRkaWFnbm9zaXMKY29uZl9tYXRyaXggPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZGljdGVkX2xhYmVscywgQWN0dWFsID0gdHJ1ZV9sYWJlbHMpCgojIEFjY3VyYWN5IE1ldHJpY3MKYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZl9tYXRyaXgpKSAvIHN1bShjb25mX21hdHJpeCkKcmVjYWxsIDwtIGNvbmZfbWF0cml4WzIsMl0gLyBzdW0oY29uZl9tYXRyaXhbMixdKQpzcGVjaWZpY2l0eSA8LSBjb25mX21hdHJpeFsxLDFdIC8gc3VtKGNvbmZfbWF0cml4WzEsXSkKCmNhdCgiQWNjdXJhY3k6IiwgYWNjdXJhY3ksICJcbiIpCmNhdCgiUmVjYWxsOiIsIHJlY2FsbCwgIlxuIikKY2F0KCJTcGVjaWZpY2l0eToiLCBzcGVjaWZpY2l0eSwgIlxuIikKYGBgCg==